package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

//缓存增强切面类
@Aspect
@Component
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RBloomFilter rBloomFilter;
    // * com.atguigu.gmall.index.service.*.*(..)
    //前置通知 任意返回值 任意方法 任意参数
    @Pointcut("execution(* com.atguigu.gmall.index.service.*.*(..))")
    public void pointCut(){}

    @Before("pointCut()")
    public void before(JoinPoint joinPoint){

        System.out.println("前置通知方法");
        //获取参数列表
        Object[] args = joinPoint.getArgs();
        System.out.println("参数列表"+ Arrays.asList(args));

        //获取方法对象 需要先获取签名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        //获取方法名
        String methodName = signature.getName();
        System.out.println("方法名"+methodName);

        //获取参数名称
        String[] parameterNames = signature.getParameterNames();
        System.out.println("参数名称 "+Arrays.asList(parameterNames));

        //获取参数类型
        Class[] parameterTypes = signature.getParameterTypes();
        System.out.println("参数类型"+parameterTypes);

    }

    //后置通知
    @After("pointCut()")
    public void after(){
        System.out.println("后置通知方法");

    }

    //使用环绕通知 增强添加了缓存注解的类
    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object cacheAdvice(ProceedingJoinPoint joinPoint) throws Throwable {

        //查询缓存需要key
        //获取目标方法
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取参数列表
        Object[] args = joinPoint.getArgs();
        String argStr = StringUtils.join(args, ",");
        //获取注解对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);
        //获取key的前缀
        String prefix = gmallCache.prefix();
        //组装key
        String key = prefix+argStr;
        //先查询缓存 存在直接返回
        String json = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)){
            //缓存存在 直接返回 将json解析为目标方法返回值类型
            return JSON.parseObject(json,method.getReturnType());
        }

        //为了防止缓存穿透 添加布隆过滤器
        boolean flag = rBloomFilter.contains(key);
        if (!flag){
            //数据不存在 直接返回
            return null;
        }
        //为了防止缓存击穿 给数据加入分布式锁
        String lockStr = gmallCache.lock();
        RLock lock = redissonClient.getLock(lockStr + argStr);
        lock.lock();
        try {
            //其他请求在获取锁等待 本次请求获取到锁 查询到了数据 放入缓存 其他求需要再次查询缓存
            String json2 = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)){
                //缓存存在 直接返回 将json解析为目标方法返回值类型
                return JSON.parseObject(json2,method.getReturnType());
            }
            //执行目标方法 将返回结果集放入缓存
            Object result = joinPoint.proceed(args);
            if (result!=null){
                //将返回结果集放入缓存 为了防止缓存雪崩 给过期时间添加随机值
                int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
                redisTemplate.opsForValue().set(key,JSON.toJSONString(result),timeout, TimeUnit.MINUTES);
            }
            return result;
        } finally {
            //解锁
            lock.unlock();
        }
    }
}
